package by.parfen.game.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import by.parfen.game.datamodel.Game;
import by.parfen.game.datamodel.Invitation;
import by.parfen.game.datamodel.Player;
import by.parfen.game.service.GameService;
import by.parfen.game.service.GameServiceListener;

public class GameServiceImpl implements GameService {

	// TODO
	// max value? recycle?
	private Long sequenceId = 0L;

	// registered players
	private Map<String, Player> players = new HashMap<>();

	// logged/active players
	private Set<String> activePlayers = new HashSet<>();

	// available players
	private Set<String> availablePlayers = new HashSet<>();

	// active invitations
	private Map<Long, Invitation> invitations = new HashMap<>();
	// the inviter and all invitations
	// what search to clear/cancel
	private Map<String, Invitation> playerInvitations = new HashMap<>();
	private Map<String, Set<Long>> playerInvitationIds = new HashMap<>();

	// active games
	private Map<Long, Game> activeGames = new HashMap<>();

	//
	private GameServiceListener listener;

	public void setListener(GameServiceListener listener) {
		this.listener = listener;
	}

	@Override
	public Player signUp(String name) {
		if (!players.containsKey(name)) {
			Player player = new Player();
			player.setName(name);
			players.put(name, player);
		}
		activePlayers.add(name);
		availablePlayers.add(name);

		Player player = players.get(name);

		// EVENT
		listener.onSignUp(player);

		return player;
	}

	@Override
	public void leave(String name) {
		activePlayers.remove(name);
		availablePlayers.remove(name);

		removeInvitationsToPlayer(name);
		removeInvitationsFromPlayer(name);

		// System.out.println("Logout, player: " + players.get(name));
		listener.onLeave(players.get(name));
	}

	private Invitation invitationExists(String inviter, String player) {
		Invitation invitation = new Invitation();
		List<Invitation> invitations = getInvitations(inviter, player);
		if (invitations != null && invitations.size() > 0) {
			invitation = invitations.get(0);
		}
		return invitation;
	}

	@Override
	public Invitation invite(String inviter, String player) {
		Invitation invitation = new Invitation();
		System.out.println(String.format("Invitation, inviter: %s, player: %s", inviter, player));
		// may be return NULL instead object?
		if (!inviter.equals(player)) {
			// check double value of invitation
			invitation = invitationExists(inviter, player);
			if (invitation == null || invitation.getId() == null) {
				invitation.setPlayer1(inviter);
				invitation.setPlayer2(player);
				invitation.setId(++sequenceId);

				invitations.put(invitation.getId(), invitation);

				// store the invitation for the inviter
				playerInvitations.put(inviter, invitation);

				listener.onInvite(invitation);
			}
		}
		return invitation;
	}

	@Override
	public Invitation cancel(Long invitationId) {
		Invitation invitation = invitations.remove(invitationId);

		if (invitation != null) {
			removeInvitationsById(invitation.getId());
			listener.onCancel(invitation);
		}

		return invitation;
	}

	@Override
	public Game accept(Long invitationId) {
		// search the Invitation by Id
		Invitation invitation = invitations.get(invitationId);

		if (invitation != null) {
			// EVENT
			// notify about
			listener.onAccept(invitation);

			// create the game using Invitation players info
			Game game = new Game(invitationId, players.get(invitation.getPlayer1()), players.get(invitation.getPlayer2()));
			// store the game as active
			activeGames.put(game.getId(), game);
			// remove players from the available players list
			availablePlayers.remove(invitation.getPlayer1());
			availablePlayers.remove(invitation.getPlayer2());

			// remove the invitation
			invitations.remove(invitationId);

			// remove others invitations
			removeInvitationsToPlayer(invitation.getPlayer1());
			removeInvitationsFromPlayer(invitation.getPlayer1());
			removeInvitationsToPlayer(invitation.getPlayer2());
			removeInvitationsFromPlayer(invitation.getPlayer2());

			// EVENT
			// notify about a new game
			listener.onGameStart(game);

			return game;
		} else {
			System.out.println("Can't find invitation by id = " + invitationId);
			for (Long entry : invitations.keySet()) {
				System.out.println("invitation.id = " + entry);
			}
			return null;
		}
	}

	private void removePlayerInvitations(String inviter, String player, Long invitationId) {
		Iterator<Entry<String, Invitation>> it = playerInvitations.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, Invitation> entry = (Entry<String, Invitation>) it.next();
			System.out.println("Check the invitation to remove: " + entry.getValue() + "... ");
			if (entry != null) {
				if (inviter != null && entry.getKey().equals(inviter)) {
					it.remove();
				}
				if (player != null && entry.getValue().getPlayer2().equals(player)) {
					it.remove();
				}
				if (invitationId != null && entry.getValue().getId().equals(invitationId)) {
					it.remove();
				}
			}
		}
	}

	private void removeInvitationsToPlayer(String player) {
		System.out.println("remove Invitations To Player " + player);
		removePlayerInvitations(null, player, null);
	}

	private void removeInvitationsFromPlayer(String inviter) {
		System.out.println("remove Invitations From Player " + inviter);
		removePlayerInvitations(inviter, null, null);
	}

	private void removeInvitationsById(Long invitationId) {
		removePlayerInvitations(null, null, invitationId);
	}

	public List<Player> getAllAvailablePlayers(String excludePlayer) {
		List<Player> result = new ArrayList<Player>();
		List<String> listOfPlayers = new ArrayList<String>(availablePlayers);
		for (String playerName : listOfPlayers) {
			if (excludePlayer != null && !playerName.equals(excludePlayer)) {
				result.add(players.get(playerName));
			}
		}
		return result;
	}

	@Override
	public List<Player> getAvailablePlayers(String excludePlayer) {
		return getAllAvailablePlayers(excludePlayer);
	}

	@Override
	public List<Game> getActiveGames() {
		List<Game> result = new ArrayList<Game>();
		List<Long> listOfGames = new ArrayList<Long>(activeGames.keySet());
		for (Long gameId : listOfGames) {
			result.add(activeGames.get(gameId));
		}
		return result;
	}

	@Override
	public void finishGame(Long gameId, Long winnerId) {
		// remove game from the list
		Game game = activeGames.get(gameId);

		activeGames.remove(gameId);

		// add players to the available players list
		availablePlayers.add(game.getPlayer1().getName());
		availablePlayers.add(game.getPlayer2().getName());

		// EVENT
		// notify about the finish
		listener.onGameFinish(gameId);

	}

	public List<Invitation> getInvitations(String inviter, String player) {
		List<Invitation> result = new ArrayList<Invitation>();
		List<Invitation> listOfInvitations = new ArrayList<Invitation>(playerInvitations.values());
		System.out.println("Search from " + inviter + " to " + player);

		for (Invitation invitation : listOfInvitations) {
			System.out.print("testing " + invitation + "... ");
			// from inviter only || to player only || from inviter to player
			// only
			if ((inviter != null && player == null && invitation.getPlayer1().equals(inviter))
					|| (inviter == null && player != null && invitation.getPlayer2().equals(player)) || (inviter != null
							&& player != null && invitation.getPlayer1().equals(inviter) && invitation.getPlayer2().equals(player))) {
				System.out.println("added");
				result.add(invitation);
			} else {
				System.out.println("skipped");

			}
		}
		return result;
	}

	@Override
	public List<Invitation> getInvitationsFromPlayer(String player) {
		return getInvitations(player, null);
	}

	@Override
	public List<Invitation> getInvitationsToPlayer(String player) {
		return getInvitations(null, player);
	}

}
